package com.zshy.base.monitor.domain.sender.factory.impl;


import com.alibaba.fastjson2.JSON;
import com.dingtalk.api.DefaultDingTalkClient;
import com.dingtalk.api.DingTalkClient;
import com.dingtalk.api.request.OapiRobotSendRequest;
import com.dingtalk.api.response.OapiRobotSendResponse;
import com.taobao.api.ApiException;
import com.zshy.base.core.contract.enums.ResultCodeEnum;
import com.zshy.base.monitor.domain.contract.command.*;
import com.zshy.base.monitor.domain.contract.enums.DingTaskMessageTypeEnum;
import com.zshy.base.monitor.domain.contract.enums.MonitorEnum;
import com.zshy.base.monitor.domain.contract.exception.MonitorException;
import com.zshy.base.monitor.domain.sender.factory.AbstractSender;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * 钉钉机器人
 *
 * @author zhouhengzhe
 */
@Slf4j
public class DingDingRobotSender extends AbstractSender {

    /**
     * token
     */
    private final String accessToken;

    /**
     * 密码
     */
    private final String secret;

    public static final String BASE_URL = "https://oapi.dingtalk.com/robot/send?access_token=";


    public DingDingRobotSender(String accessToken, String secret) {
        if (StringUtils.isEmpty(accessToken)
                || StringUtils.isEmpty(secret)) {
            throw new MonitorException(ResultCodeEnum.ARGUMENT_NULL);
        }
        this.accessToken = accessToken;
        this.secret = secret;
    }

    private DingTalkClient dingTalkClient() {
        return new DefaultDingTalkClient(BASE_URL + this.accessToken + sign(this.secret));
    }

    private static String sign(String secret) {
        try {
            Long timestamp = System.currentTimeMillis();
            String stringToSign = timestamp + "\n" + secret;
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
            byte[] signData = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8));
            String sign = URLEncoder.encode(new String(Base64.encodeBase64(signData)), StandardCharsets.UTF_8);
            return "&timestamp=" + timestamp + "&sign=" + sign;
        } catch (Exception e) {
            throw new MonitorException(e);
        }
    }

    /**
     * 发送link 类型消息（钉钉）
     * <a href="https://open.dingtalk.com/document/orgapp/message-types-supported-by-enterprise-internal-robots#title-ymf-os7-j0c">...</a>
     *
     * @param command 消息内容
     * @return 是否发送成功
     */
    @Override
    public Boolean sendMessageByLink(SendMessageByLinkCommand command) {
        if (StringUtils.isEmpty(command.getTitle())
                || StringUtils.isEmpty(command.getText())
                || StringUtils.isEmpty(command.getMessageUrl())) {
            log.warn("title or text or messageUrl is null");
            return Boolean.FALSE;
        }
        //参数	参数类型	必须	说明
        //msgtype	String	是	消息类型，此时固定为：link
        //title	String	是	消息标题
        //text	String	是	消息内容。如果太长只会部分展示
        //messageUrl	String	是	点击消息跳转的URL
        //picUrl	String	否	图片URL
        OapiRobotSendRequest.Link link = new OapiRobotSendRequest.Link();
        link.setTitle(command.getTitle());
        link.setText(command.getText());
        link.setMessageUrl(command.getMessageUrl());
        link.setPicUrl(command.getPicUrl());

        OapiRobotSendRequest request = new OapiRobotSendRequest();
        request.setMsgtype(DingTaskMessageTypeEnum.LINK.getCode());
        request.setLink(link);

        request.setAt(getAt(command.getMobileList(),
                command.getAtUserIds(),
                command.getIsAtAll()));

        OapiRobotSendResponse response;
        try {
            response = dingTalkClient().execute(request);
            log.info("【发送link类型消息】消息响应结果：{}", JSON.toJSONString(response));
        } catch (ApiException e) {
            throw new MonitorException(e);
        }
        return response.isSuccess();
    }

    /**
     * 发送ActionCard 类型消息（独立跳转actionCard类型）（钉钉）
     * <a href="https://open.dingtalk.com/document/orgapp/message-types-supported-by-enterprise-internal-robots#title-q1d-cx6-k3v">...</a>
     *
     * @param command 消息内容
     * @return 是否发送成功
     */
    @Override
    public Boolean sendMessageByActionCardSingle(SendMessageByActionCardSingleCommand command) {
        if (StringUtils.isEmpty(command.getTitle())
                || StringUtils.isEmpty(command.getMarkdownText())) {
            return Boolean.FALSE;
        }
        //参数	类型	必选	说明
        //    msgtype	string	true	此消息类型为固定actionCard
        //    title	string	true	首屏会话透出的展示内容
        //    text	string	true	markdown格式的消息
        //    singleTitle	string	true	单个按钮的方案。(设置此项和singleURL后btns无效)
        //    singleURL	string	true	点击singleTitle按钮触发的URL
        //    btnOrientation	string	false	0-按钮竖直排列，1-按钮横向排列
        //    hideAvatar	string	false	0-正常发消息者头像，1-隐藏发消息者头像
        OapiRobotSendRequest.Actioncard actionCard = new OapiRobotSendRequest.Actioncard();
        actionCard.setTitle(command.getTitle());
        actionCard.setText(command.getMarkdownText());
        actionCard.setSingleTitle(command.getSingleTitle());
        actionCard.setSingleURL(command.getSingleUrl());
        // 此处默认为0
        actionCard.setBtnOrientation(command.getBtnOrientationEnum().getCode());
        // 此处默认为0
        actionCard.setHideAvatar(command.getHideAvatarEnum().getCode());
        OapiRobotSendRequest request = new OapiRobotSendRequest();
        request.setMsgtype(DingTaskMessageTypeEnum.ACTION_CARD.getCode());
        request.setActionCard(actionCard);
        OapiRobotSendResponse response;
        try {
            response = dingTalkClient().execute(request);
            log.info("【整体跳转ActionCard类型的消息发送】消息响应结果：{}", JSON.toJSONString(response));
        } catch (ApiException e) {
            throw new MonitorException(e);
        }
        return response.isSuccess();
    }

    /**
     * 发送ActionCard 类型消息(整体跳转actionCard类型)（钉钉）
     * <a href="https://open.dingtalk.com/document/orgapp/message-types-supported-by-enterprise-internal-robots#title-q1d-cx6-k3v">...</a>
     *
     * @param command 消息内容
     * @return 是否发送成功
     */
    @Override
    public Boolean sendMessageByActionCardMulti(SendMessageByActionCardMultiCommand command) {
        if (StringUtils.isEmpty(command.getTitle())
                || StringUtils.isEmpty(command.getMarkdownText())
                || CollectionUtils.isEmpty(command.getBtns())) {
            return Boolean.FALSE;
        }
        //参数	类型	必选	说明
        //msgtype	string	true	此消息类型为固定actionCard
        //title	string	true	首屏会话透出的展示内容
        //text	string	true	markdown格式的消息
        //btns	array	true	按钮的信息：title-按钮方案，actionURL-点击按钮触发的URL
        //btnOrientation	string	false	0-按钮竖直排列，1-按钮横向排列
        //hideAvatar	string	false	0-正常发消息者头像，1-隐藏发消息者头像
        OapiRobotSendRequest.Actioncard actionCard = new OapiRobotSendRequest.Actioncard();
        actionCard.setTitle(command.getTitle());
        actionCard.setText(command.getMarkdownText());
        // 此处默认为0
        actionCard.setBtnOrientation(command.getBtnOrientationEnum().getCode());
        // 此处默认为0
        actionCard.setHideAvatar(command.getHideAvatarEnum().getCode());
        actionCard.setBtns(command.getBtns());
        OapiRobotSendRequest request = new OapiRobotSendRequest();
        request.setMsgtype(DingTaskMessageTypeEnum.ACTION_CARD.getCode());
        request.setActionCard(actionCard);
        OapiRobotSendResponse response;
        try {
            response = dingTalkClient().execute(request);
            log.info("【独立跳转ActionCard类型 消息发送】消息响应结果：{}", JSON.toJSONString(response));
        } catch (ApiException e) {
            throw new MonitorException(e);
        }
        return response.isSuccess();
    }

    /**
     * 发送feedCard 类型消息（钉钉）
     * <a href="https://open.dingtalk.com/document/orgapp/message-types-supported-by-enterprise-internal-robots#title-g93-70j-1qy">...</a>
     *
     * @param command 消息内容
     * @return 是否发送成功
     */
    @Override
    public Boolean sendMessageByFeedCard(SendMessageByFeedCardCommand command) {
        if (CollectionUtils.isEmpty(command.getLinks())) {
            log.warn("kinks is null");
            return Boolean.FALSE;
        }
        //msgtype	string	true	此消息类型为固定feedCard
        //title	string	true	单条信息文本
        //messageURL	string	true	点击单条信息到跳转链接
        //picURL	string	true	单条信息后面图片的URL
        OapiRobotSendRequest.Feedcard feedcard = new OapiRobotSendRequest.Feedcard();
        feedcard.setLinks(command.getLinks());
        OapiRobotSendRequest request = new OapiRobotSendRequest();
        request.setMsgtype(DingTaskMessageTypeEnum.FEED_CARD.getCode());
        request.setFeedCard(feedcard);
        request.setAt(getAt(command.getMobileList(),
                command.getAtUserIds(),
                command.getIsAtAll()));
        OapiRobotSendResponse response;
        try {
            response = dingTalkClient().execute(request);
            log.info("【发送FeedCard类型消息】消息响应结果：{}", JSON.toJSONString(response));
        } catch (ApiException e) {
            throw new MonitorException(e);
        }
        return response.isSuccess();
    }

    /**
     * 发送类型
     *
     * @return
     */
    @Override
    public String getType() {
        return MonitorEnum.DING_TASK.getCode();
    }

    /**
     * 发送文本消息（钉钉，企微都有）
     * <a href="https://open.dingtalk.com/document/orgapp/message-types-supported-by-enterprise-internal-robots#title-3tg-1on-mru">...</a>
     * <a href="https://developer.work.weixin.qq.com/document/path/99110#%E6%96%87%E6%9C%AC%E7%B1%BB%E5%9E%8B">...</a>
     *
     * @param command 消息内容
     * @return 是否发送成功
     */
    @Override
    public Boolean sendMessageByText(SendMessageByTextCommand command) {
        if (StringUtils.isEmpty(command.getContent())) {
            log.warn("content is null");
            return Boolean.FALSE;
        }
        //参数	参数类型	必须	说明
        //msgtype	String	是	消息类型，此时固定为：text
        //content	String	是	消息内容
        //atMobiles	Array	否	被@人的手机号(在content里添加@人的手机号)
        //isAtAll	bool	否	@所有人时：true，否则为：false
        OapiRobotSendRequest.Text text = new OapiRobotSendRequest.Text();
        text.setContent(command.getContent());
        OapiRobotSendRequest request = new OapiRobotSendRequest();
        request.setAt(getAt(command.getMobileList(),
                command.getAtUserIds(),
                command.getIsAtAll()));
        request.setMsgtype(DingTaskMessageTypeEnum.TEXT.getCode());
        request.setText(text);
        OapiRobotSendResponse response;
        try {
            response = dingTalkClient().execute(request);
            log.info("【发送普通文本消息】消息响应结果：{}", JSON.toJSONString(response));
        } catch (ApiException e) {
            throw new MonitorException(e);
        }
        return response.isSuccess();
    }

    /**
     * 发送Markdown 编辑格式的消息（钉钉，企微都有）
     * <a href="https://open.dingtalk.com/document/orgapp/message-types-supported-by-enterprise-internal-robots#title-n6d-5gn-lnw">...</a>
     * <a href="https://developer.work.weixin.qq.com/document/path/99110#markdown%E7%B1%BB%E5%9E%8B">...</a>
     *
     * @param command 消息内容
     * @return 是否发送成功
     */
    @Override
    public Boolean sendMessageByMarkdown(SendMessageByMarkdownCommand command) {
        if (StringUtils.isEmpty(command.getTitle())
                || StringUtils.isEmpty(command.getMarkdownText())) {
            return Boolean.FALSE;
        }
        //参数	类型	必选	说明
        //msgtype	String	是	此消息类型为固定markdown
        //title	String	是	首屏会话透出的展示内容
        //text	String	是	markdown格式的消息
        //atMobiles	Array	否	被@人的手机号(在text内容里要有@手机号)
        //isAtAll	bool	否	@所有人时：true，否则为：false
        OapiRobotSendRequest.Markdown markdown = new OapiRobotSendRequest.Markdown();
        markdown.setTitle(command.getTitle());
        markdown.setText(command.getMarkdownText());
        OapiRobotSendRequest request = new OapiRobotSendRequest();
        request.setMsgtype(DingTaskMessageTypeEnum.MARKDOWN.getCode());
        request.setMarkdown(markdown);
        request.setAt(getAt(command.getMobileList(),
                command.getAtUserIds(),
                command.getIsAtAll()));
        OapiRobotSendResponse response;
        try {
            response = dingTalkClient().execute(request);
            log.info("【发送Markdown 编辑格式的消息】消息响应结果：{}", JSON.toJSONString(response));
        } catch (ApiException e) {
            throw new MonitorException(e);
        }
        return response.isSuccess();
    }


    /**
     * 获取需要被@的人
     *
     * @param mobileList 被@人的手机号
     * @param atUserIds  被@人的userId
     * @param isAtAll    是否@所有人
     * @return
     */
    private static OapiRobotSendRequest.At getAt(List<String> mobileList, List<String> atUserIds, Boolean isAtAll) {
        OapiRobotSendRequest.At at = new OapiRobotSendRequest.At();
        if (!CollectionUtils.isEmpty(mobileList)) {
            // 发送消息并@ 以下手机号联系人
            at.setAtMobiles(mobileList);
        }
        if (!CollectionUtils.isEmpty(atUserIds)) {
            // 发送消息并@ 以下用户id
            at.setAtUserIds(atUserIds);
        }
        at.setIsAtAll(isAtAll);
        return at;
    }
}
